Skip to content

fix(sso): resolve infinite redirect loop for non-logged-in visitors on subsites#366

Open
superdav42 wants to merge 4 commits intomainfrom
bugfix/sso-redirect-loop
Open

fix(sso): resolve infinite redirect loop for non-logged-in visitors on subsites#366
superdav42 wants to merge 4 commits intomainfrom
bugfix/sso-redirect-loop

Conversation

@superdav42
Copy link
Collaborator

@superdav42 superdav42 commented Mar 12, 2026

Summary

Fixes the SSO infinite redirect loop that causes subsite pages to endlessly reload with ?sso_verify=invalid;sso_error=User+not+logged+in for non-logged-in visitors. Reported by customer Rosina Bignall — persisted through 2.4.11 and 2.4.12 despite the earlier partial fix.

Root Causes Fixed

  1. JSONP + unattached broker redirect: handle_broker() called wp_safe_redirect() even for JSONP <script> tag requests. The browser followed the 302 transparently, but the main site responded with another redirect (not JavaScript), so the wu.sso() callback never fired and the wu_sso_denied cookie was never set. Fix: Return a JSONP error response instead, so the JS callback fires and sets the denied cookie.

  2. Incognito redirect loop in JS: When the JSONP error response fired in incognito mode, the JS redirected to the server URL as a fallback. This triggered the redirect flow -> sso_verify=invalid -> redirect back -> JS fires again -> detects incognito -> redirects again -> infinite loop. Fix: Removed the incognito redirect path entirely — the denied cookie now prevents re-entry in all cases.

  3. Dead code cleanup: Removed detectIncognito library dependency and all incognito detection code from sso.js since the incognito redirect path has been removed.

Files Changed

  • inc/sso/class-sso.php — JSONP error response for unattached broker, removed wu-detect-incognito script dependency
  • assets/js/sso.js — Removed incognito redirect path and detection code
  • assets/js/sso.min.js — Rebuilt
  • tests/WP_Ultimo/SSO/SSO_Test.php — Updated Content-Type header count (2->3), added tests for JSONP unattached broker fix and incognito removal

Testing

  • PHPCS: clean
  • PHPStan: only pre-existing errors (same as main — Symfony cache classes)
  • Test assertions: all pass (bootstrap blocked by unrelated missing Plesk class)
  • Needs manual testing on a multisite with SSO enabled, visiting a subsite while not logged in

Summary by CodeRabbit

  • New Features

    • Added a public way to mark SSO denial that sets a retry-throttling cookie to reduce repeated attempts.
  • Bug Fixes

    • SSO now returns proper JSONP error responses instead of performing problematic redirects, removes stuck loading overlays, and prevents redirect loops.
  • Refactor

    • Removed incognito detection from the SSO flow and simplified script injection and control flow.
  • Tests

    • Added extensive unit tests plus a new end-to-end test for SSO redirect-loop scenarios; CI updated to run the new e2e test.

…n subsites

Three root causes fixed:

1. handle_broker JSONP + unattached broker: When a JSONP <script> tag
   request hit the broker and it wasn't attached, the code did
   wp_safe_redirect to the main site. The browser follows 302s
   transparently for script tags, but the main site's response was
   another redirect (not JavaScript), so the wu.sso() callback never
   fired and the wu_sso_denied cookie was never set. Now returns a
   JSONP error response instead, letting the JS handle it gracefully.

2. Incognito redirect loop in JS: When the JSONP error response fired
   and the browser was in incognito mode, the JS redirected to the
   server URL as a fallback. This triggered the redirect flow which
   returned sso_verify=invalid, which redirected back, and the cycle
   repeated. Removed the incognito redirect path entirely - the denied
   cookie now prevents re-entry in all cases.

3. Removed unused incognito detection: The detectIncognito library
   dependency and all incognito-related code are no longer needed
   since the incognito redirect path has been removed.

Reported-by: Rosina Bignall <rosina@arbe.co>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 12, 2026

📝 Walkthrough

Walkthrough

Replaces incognito-based SSO retry logic with a cookie-based denial flow (wu_sso_denied), adds a public window.wu.sso_denied() API, simplifies script injection, changes broker JSONP handling to return an error payload (no redirect), and adds substantial unit and e2e tests plus a CI step for SSO redirect-loop testing.

Changes

Cohort / File(s) Summary
Client SSO script
assets/js/sso.js
Removes incognito detection and check_for_incognito_window; injects CSS/overlay earlier; prepares script element attributes before insertion; introduces window.wu.sso_denied() to set wu_sso_denied cookie; on non-success SSO responses calls sso_denied() and removes overlay; preserves successful redirect logic.
Server SSO logic
inc/sso/class-sso.php
When broker is unattached and request is JSONP, returns a JSONP error payload (no redirect) with proper Content-Type and 200 status; rearranges return_url/attach URL logic; removes incognito-related script registration and registers wu-sso script with only wu-cookie-helpers dependency.
Unit tests
tests/WP_Ultimo/SSO/SSO_Test.php
Adds many unit tests covering SSO config, URL/encode-decode, strategy/return type handling, broker attach/JSONP error path, session handler, and various edge cases including Content-Type behavior for JSONP paths.
E2E tests & CI
.github/workflows/e2e.yml, tests/e2e/cypress/integration/065-sso-redirect-loop.spec.js
Adds a CI step to run a new Cypress spec that verifies SSO redirect-loop prevention, wu_sso_denied cookie behavior, JSONP error response handling, absence of incognito code in served JS, and cross-domain cookie scenarios.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

"I hopped through code with a twitchy nose,
Cookies and callbacks where the north wind blows.
No more incognito fog in the lane,
A denial cookie keeps loops tame.
— 🐰"

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main fix: resolving an infinite redirect loop for non-logged-in visitors on subsites, which is the primary objective of the entire changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bugfix/sso-redirect-loop

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

🔨 Build Complete - Ready for Testing!

📦 Download Build Artifact (Recommended)

Download the zip build, upload to WordPress and test:

🌐 Test in WordPress Playground (Very Experimental)

Click the link below to instantly test this PR in your browser - no installation needed!
Playground support for multisite is very limitied, hopefully it will get better in the future.

🚀 Launch in Playground

Login credentials: admin / password

@github-actions
Copy link

🔨 Build Complete - Ready for Testing!

📦 Download Build Artifact (Recommended)

Download the zip build, upload to WordPress and test:

🌐 Test in WordPress Playground (Very Experimental)

Click the link below to instantly test this PR in your browser - no installation needed!
Playground support for multisite is very limitied, hopefully it will get better in the future.

🚀 Launch in Playground

Login credentials: admin / password

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
tests/WP_Ultimo/SSO/SSO_Test.php (2)

599-623: Don’t count header strings across the whole file.

A raw count is brittle and can pass for the wrong reason. Matching each JSONP branch independently will survive harmless refactors and still prove the intended paths set the header.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/WP_Ultimo/SSO/SSO_Test.php` around lines 599 - 623, The test currently
counts header occurrences across the whole file, which is brittle; update
test_handle_broker_source_sets_javascript_content_type_for_jsonp to
independently assert that each JSONP branch sets the Content-Type header by
matching each branch's specific code block instead of a global count—locate the
three JSONP branches (the handle_server JSONP block and the two handle_broker
JSONP branches for unattached and attached brokers) and replace the
preg_match_all assertion with three targeted preg_match assertions that look for
the header(...) call within each branch's surrounding code (use unique anchors
such as the handle_server and handle_broker identifiers or adjacent distinctive
lines/comments to scope each regex).

419-447: Assert the loop-prevention behavior directly.

These checks still key off implementation tokens. wu.sso( can coexist with a wp_safe_redirect() in the same JSONP branch, and is_incognito can disappear while the fallback redirect comes back under another name. I’d assert the absence of wp_safe_redirect() in the unattached JSONP branch and the absence of any error-path window.location.replace(...) fallback instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/WP_Ultimo/SSO/SSO_Test.php` around lines 419 - 447, Change the tests to
assert behavior (not just tokens): in
test_handle_broker_source_returns_jsonp_error_for_unattached_broker, tighten the
regex or add an assertion to ensure the JSONP branch that calls wu.sso(...) does
NOT contain wp_safe_redirect() (search for the JSONP branch surrounding
isAttached() / 'jsonp' === $response_type and assert absence of wp_safe_redirect
within that block); in test_sso_js_does_not_contain_incognito_redirect, replace
the is_incognito token check with an assertion that the built SSO JS does not
contain any error-path redirect fallbacks (e.g., absence of
window.location.replace( or window.location.href= used as a fallback) so error
paths cannot trigger client-side redirect loops.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@inc/sso/class-sso.php`:
- Around line 545-570: The JSONP branch currently treats $broker->isAttached()
=== false as a final error and returns wu.sso(... code:0 ...) which causes
wu.sso_denied() and sets the wu_sso_denied cookie, breaking first-time
attachment; change the JSONP handling so that when response_type === 'jsonp' and
the broker is not attached you do not emit a denial but instead initiate the
attach flow: call $broker->getAttachUrl() (or otherwise build the attach URL)
and return a JSONP payload that instructs the client JS to start the attach
handshake (e.g., provide an "attach_url" and success code or a specific action
field) so the front-end can open/redirect to the attach URL rather than calling
wu.sso_denied(); ensure you do not set the denial cookie in this path and keep
status_header(200).

---

Nitpick comments:
In `@tests/WP_Ultimo/SSO/SSO_Test.php`:
- Around line 599-623: The test currently counts header occurrences across the
whole file, which is brittle; update
test_handle_broker_source_sets_javascript_content_type_for_jsonp to
independently assert that each JSONP branch sets the Content-Type header by
matching each branch's specific code block instead of a global count—locate the
three JSONP branches (the handle_server JSONP block and the two handle_broker
JSONP branches for unattached and attached brokers) and replace the
preg_match_all assertion with three targeted preg_match assertions that look for
the header(...) call within each branch's surrounding code (use unique anchors
such as the handle_server and handle_broker identifiers or adjacent distinctive
lines/comments to scope each regex).
- Around line 419-447: Change the tests to assert behavior (not just tokens): in
test_handle_broker_source_returns_jsonp_error_for_unattached_broker, tighten the
regex or add an assertion to ensure the JSONP branch that calls wu.sso(...) does
NOT contain wp_safe_redirect() (search for the JSONP branch surrounding
isAttached() / 'jsonp' === $response_type and assert absence of wp_safe_redirect
within that block); in test_sso_js_does_not_contain_incognito_redirect, replace
the is_incognito token check with an assertion that the built SSO JS does not
contain any error-path redirect fallbacks (e.g., absence of
window.location.replace( or window.location.href= used as a fallback) so error
paths cannot trigger client-side redirect loops.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c6d2473f-a6af-4cfe-b030-63c82b3c190b

📥 Commits

Reviewing files that changed from the base of the PR and between e8372af and 94571b4.

⛔ Files ignored due to path filters (1)
  • assets/js/sso.min.js is excluded by !**/*.min.js
📒 Files selected for processing (3)
  • assets/js/sso.js
  • inc/sso/class-sso.php
  • tests/WP_Ultimo/SSO/SSO_Test.php

Comment on lines 545 to +570
if ( ! $broker->isAttached()) {
$return_url = $this->get_current_url();

if ( 'jsonp' === $response_type) {
$attach_url = $broker->getAttachUrl(
[
'_jsonp' => '1',
]
);
} else {
$attach_url = $broker->getAttachUrl(
[
'return_url' => $return_url,
]
/*
* For JSONP requests (initiated by a <script> tag), we must NOT
* redirect — the browser follows 302s transparently for script
* tags, but the final response from the server will be a redirect
* (not JavaScript), so the wu.sso() callback never fires.
* Instead, return a JSONP error so the JS can handle it gracefully
* and set the wu_sso_denied cookie to prevent further attempts.
*/
if ('jsonp' === $response_type) {
header('Content-Type: application/javascript; charset=utf-8');

printf(
'wu.sso(%s, %d);',
wp_json_encode(
[
'code' => 0,
'message' => 'Broker not attached',
]
),
200
);

status_header(200);

exit;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Inspect broker attachment state:"
rg -n -C4 'class\s+SSO_Broker|function\s+isAttached\s*\(|function\s+getAttachUrl\s*\(' --glob '*.php'

echo
echo "Inspect the JSONP entrypoint and the unattached broker branch:"
rg -n -C4 '_jsonp=1|isAttached\(\)|getAttachUrl\(|sso_verify|wu\.sso\(' inc/sso/class-sso.php assets/js/sso.js

Repository: Ultimate-Multisite/ultimate-multisite

Length of output: 6504


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Find the parent Broker class:"
find . -type f -name '*.php' | xargs rg -l 'class Broker' | head -20

echo
echo "Search for Broker class definition:"
rg -n -A 20 'class Broker' --glob '*.php' | grep -A 20 'class Broker [^a-zA-Z_]'

echo
echo "Find isAttached implementation in parent class:"
rg -n -B 2 -A 10 'function isAttached' --glob '*.php'

Repository: Ultimate-Multisite/ultimate-multisite

Length of output: 112


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Search for Broker class references and imports:"
rg -n 'Broker|extends Broker|use.*Broker' inc/sso/class-sso-broker.php

echo
echo "Check composer.json for dependencies:"
cat composer.json 2>/dev/null | head -50 || echo "No composer.json at root"

echo
echo "Look for vendor directories or external class files:"
find . -type f -name '*.php' -path '*/vendor/*' 2>/dev/null | grep -i broker | head -10

echo
echo "Broader search for isAttached definition anywhere:"
rg -n 'function isAttached|isAttached.*=' --glob '*.php' -A 5

Repository: Ultimate-Multisite/ultimate-multisite

Length of output: 1863


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Search vendor directory for Jasny Broker:"
find vendor -type f -name '*.php' -path '*Jasny*Broker*' 2>/dev/null | head -20

echo
echo "Look for get_broker() method to understand initialization:"
rg -n -A 15 'function get_broker|private.*broker' inc/sso/class-sso.php

echo
echo "Check if there's any session state setup before line 545:"
sed -n '500,545p' inc/sso/class-sso.php | cat -n

Repository: Ultimate-Multisite/ultimate-multisite

Length of output: 122


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Find get_broker() method definition:"
rg -n -A 20 'function get_broker' inc/sso/class-sso.php

echo
echo "Show context around line 545 (the isAttached check):"
sed -n '510,580p' inc/sso/class-sso.php | cat -n

echo
echo "Search for where broker attachment is set or initialized:"
rg -n 'setAttached|attach|token|session' inc/sso/class-sso.php | head -30

Repository: Ultimate-Multisite/ultimate-multisite

Length of output: 4156


🌐 Web query:

Jasny SSO Broker isAttached() session state fresh request

💡 Result:

In jasny/sso the broker’s isAttached() does not make a “fresh” call to the SSO server and it does not validate the remote server-side session on each request. It only checks whether the broker has a stored verification code in its client-side state (cookie/session). Concretely, isAttached() returns true iff getVerificationCode() !== null. [1]

What that means for “fresh request / session state”:

  • On a new HTTP request (new PHP process), you typically create a new Broker(...). When you call isAttached(), it will read state from the configured state handler (default: cookies) and decide based on whether the sso_verify_{broker} value exists. [1][2]
  • That sso_verify value is only stored after you complete the attach + verify flow (i.e., after redirect back with ?sso_verify=..., you must call $broker->verify($_GET['sso_verify']);, which persists it into state). [1][2]
  • If you want to force a re-attach (i.e., treat every request as “fresh”), clear the stored state (clearToken() clears both token and verify state) and redirect to getAttachUrl() again. [1]

Common reason isAttached() is “false every request”: the verify/token cookies aren’t being persisted/sent back (cookie domain/path/secure/SameSite issues), or you switched to session storage but didn’t session_start() before using withTokenIn(new Session()). [2]

Sources:
[1] jasny/sso Broker::isAttached() / verify() implementation (Scrutinizer)
[2] jasny/sso README usage showing isAttached(), attach redirect, and verify() (Packagist)


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Check what JS does with JSONP error (code 0):"
sed -n '1,120p' assets/js/sso.js | cat -n

echo
echo "Look for wu.sso_denied definition and what it does:"
rg -n -A 10 'sso_denied|wu\.sso_denied' assets/js/sso.js

Repository: Ultimate-Multisite/ultimate-multisite

Length of output: 4762


The unattached JSONP branch blocks the entire SSO attach flow for fresh browser sessions.

When isAttached() returns false (the expected state for a fresh session), the JSONP endpoint returns error code 0 instead of initiating the attach handshake. The JavaScript receives this error, calls wu.sso_denied() which sets a denial cookie, and the normal attach flow at lines 573–579 ($broker->getAttachUrl()) is never reached for JSONP requests. This prevents SSO from working at all for new users.

The Jasny SSO Broker::isAttached() checks only whether a stored verification code exists in session/cookies. On the first request, this is always false, so the JSONP endpoint must either initiate the attach redirect or provide another path to attachment—not deny it outright.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@inc/sso/class-sso.php` around lines 545 - 570, The JSONP branch currently
treats $broker->isAttached() === false as a final error and returns wu.sso(...
code:0 ...) which causes wu.sso_denied() and sets the wu_sso_denied cookie,
breaking first-time attachment; change the JSONP handling so that when
response_type === 'jsonp' and the broker is not attached you do not emit a
denial but instead initiate the attach flow: call $broker->getAttachUrl() (or
otherwise build the attach URL) and return a JSONP payload that instructs the
client JS to start the attach handshake (e.g., provide an "attach_url" and
success code or a specific action field) so the front-end can open/redirect to
the attach URL rather than calling wu.sso_denied(); ensure you do not set the
denial cookie in this path and keep status_header(200).

Add 065-sso-redirect-loop.spec.js with 6 tests covering:
- Non-logged-in visitor loads subsite without redirect loop
- sso_verify=invalid param handled without looping
- JSONP endpoint returns JS error (not redirect) for unattached broker
- No incognito detection code in served SSO JavaScript
- wu_sso_denied cookie prevents SSO re-triggering
- SSO still works for logged-in users (regression check)

Also add SSO tests (060 + 065) to the CI E2E workflow.
@github-actions
Copy link

🔨 Build Complete - Ready for Testing!

📦 Download Build Artifact (Recommended)

Download the zip build, upload to WordPress and test:

🌐 Test in WordPress Playground (Very Experimental)

Click the link below to instantly test this PR in your browser - no installation needed!
Playground support for multisite is very limitied, hopefully it will get better in the future.

🚀 Launch in Playground

Login credentials: admin / password

Remove the 'SSO still works for logged-in users' test from 065 — it
fails due to wp-env domain mapping limitation (127.0.0.1 returns 302),
not a code bug. This scenario is already covered by the pre-existing
060-sso-cross-domain.spec.js.

Simplify the CI SSO test step to run only 065 (redirect loop
prevention). The 060 spec was never in CI before this PR and always
fails in the wp-env environment.
@github-actions
Copy link

🔨 Build Complete - Ready for Testing!

📦 Download Build Artifact (Recommended)

Download the zip build, upload to WordPress and test:

🌐 Test in WordPress Playground (Very Experimental)

Click the link below to instantly test this PR in your browser - no installation needed!
Playground support for multisite is very limitied, hopefully it will get better in the future.

🚀 Launch in Playground

Login credentials: admin / password

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
tests/e2e/cypress/integration/065-sso-redirect-loop.spec.js (3)

41-45: Redundant cookie clearing calls.

cy.clearAllCookies() clears cookies across all domains, making the preceding cy.clearCookies() call unnecessary.

♻️ Suggested simplification
   beforeEach(() => {
     // Clear all cookies to simulate a fresh non-logged-in visitor.
-    cy.clearCookies();
     cy.clearAllCookies();
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/cypress/integration/065-sso-redirect-loop.spec.js` around lines 41
- 45, The beforeEach hook contains redundant cookie-clearing calls:
cy.clearCookies() followed by cy.clearAllCookies(); remove the unnecessary
cy.clearCookies() call and keep only cy.clearAllCookies() in the beforeEach
block so cookies are cleared across all domains without duplication (locate the
beforeEach in 065-sso-redirect-loop.spec.js and update the two calls
accordingly).

167-171: Test may not verify external script file contents.

doc.documentElement.outerHTML only includes inline scripts and HTML content. If sso.js is loaded as an external file (via <script src="...">), its contents won't be in the HTML and the test won't catch incognito code in the external file.

Consider fetching the actual JS file to verify its contents:

♻️ Suggested enhancement
     // Verify no incognito-related code in the page.
     cy.document().then((doc) => {
       const html = doc.documentElement.outerHTML;
       expect(html).to.not.include("detectIncognito");
       expect(html).to.not.include("is_incognito");
     });
+
+    // Also verify the external sso.js file content.
+    cy.get('script[src*="sso"]').first().invoke('attr', 'src').then((src) => {
+      if (src) {
+        cy.request(src).then((response) => {
+          expect(response.body).to.not.include("detectIncognito");
+          expect(response.body).to.not.include("is_incognito");
+        });
+      }
+    });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/cypress/integration/065-sso-redirect-loop.spec.js` around lines 167
- 171, The test currently inspects only inline HTML via cy.document().then and
misses external JS; update the test to locate any <script> elements that
reference the SSO script (e.g., find script elements whose src contains "sso.js"
or matches your SSO bundle name) and for each such script use cy.request to
fetch the external file and assert the response body does not include
"detectIncognito" or "is_incognito"; keep the existing inline check
(documentElement.outerHTML) but add these cy.get/cy.document queries and
cy.request assertions around the script src values so external script contents
are validated as well.

199-203: Hardcoded wait may cause flakiness.

The cy.wait(2000) is a timing-based approach. While acceptable for verifying no requests were made, this could cause unnecessary slowdown or intermittent failures in CI.

Consider reducing the wait or adding a comment explaining the rationale:

♻️ Minor suggestion
     // Verify no SSO JSONP request was made (the script should have skipped it).
-    // Wait briefly to ensure no late requests fire.
-    cy.wait(2000);
+    // Wait briefly to ensure no late requests fire. SSO script fires on DOMContentLoaded,
+    // so 500ms is sufficient buffer for any async initialization.
+    cy.wait(500);
     cy.get("@ssoRequest.all").should("have.length", 0);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/cypress/integration/065-sso-redirect-loop.spec.js` around lines 199
- 203, The hardcoded cy.wait(2000) creates flakiness and slowdown; remove the
fixed wait and instead assert against the aliased request with an explicit
timeout by replacing the wait + cy.get("@ssoRequest.all").should("have.length",
0) pattern with a single timed assertion like cy.get("@ssoRequest.all", {
timeout: 2000 }).should("have.length", 0) (or reduce the wait to a much smaller
value if removal isn't possible) and add a brief comment referencing the reason;
locate this change around the assertion that checks the "@ssoRequest.all" alias
in the 065-sso-redirect-loop.spec.js test.
.github/workflows/e2e.yml (1)

186-193: Inconsistent error handling compared to other test steps.

This step doesn't use set +e and continues-on-failure pattern like the Checkout Tests (lines 151-184) and Stripe Tests (lines 196-238). If the SSO test fails, CI will fail immediately without running subsequent tests.

This may be intentional if SSO is considered a blocking dependency, but it's inconsistent with the workflow's pattern of running all tests and aggregating failures.

♻️ For consistency, consider matching the error handling pattern
       - name: Run SSO Redirect Loop Tests (After Setup)
         id: sso-tests
         run: |
+          set +e
           echo "=== Starting SSO Redirect Loop Tests ==="
           npx cypress run \
             --config-file cypress.config.test.js \
             --spec "tests/e2e/cypress/integration/065-sso-redirect-loop.spec.js" \
             --browser ${{ matrix.browser }}
+          
+          SSO_EXIT_CODE=$?
+          if [ $SSO_EXIT_CODE -ne 0 ]; then
+            echo "❌ SSO redirect loop tests failed with exit code $SSO_EXIT_CODE"
+            exit 1
+          fi
+          echo "✅ SSO redirect loop tests passed!"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/e2e.yml around lines 186 - 193, The SSO step ("Run SSO
Redirect Loop Tests (After Setup)", id: sso-tests) currently fails the job
immediately on test failure; change it to match the Checkout and Stripe steps by
disabling strict exit checking before running Cypress, capturing the exit code,
restoring strict mode, and exporting the result so failures are aggregated
instead of aborting CI. Concretely: add a leading "set +e" before the npx
cypress run line, run the command, capture its exit code into a variable (e.g.,
SSO_TESTS_EXIT_CODE=$?), then run "set -e" and emit the exit code to GitHub
Actions (e.g., echo "sso_tests_exit_code=$SSO_TESTS_EXIT_CODE" >>
$GITHUB_OUTPUT) so downstream logic can aggregate/report failures consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.github/workflows/e2e.yml:
- Around line 186-193: The SSO step ("Run SSO Redirect Loop Tests (After
Setup)", id: sso-tests) currently fails the job immediately on test failure;
change it to match the Checkout and Stripe steps by disabling strict exit
checking before running Cypress, capturing the exit code, restoring strict mode,
and exporting the result so failures are aggregated instead of aborting CI.
Concretely: add a leading "set +e" before the npx cypress run line, run the
command, capture its exit code into a variable (e.g., SSO_TESTS_EXIT_CODE=$?),
then run "set -e" and emit the exit code to GitHub Actions (e.g., echo
"sso_tests_exit_code=$SSO_TESTS_EXIT_CODE" >> $GITHUB_OUTPUT) so downstream
logic can aggregate/report failures consistently.

In `@tests/e2e/cypress/integration/065-sso-redirect-loop.spec.js`:
- Around line 41-45: The beforeEach hook contains redundant cookie-clearing
calls: cy.clearCookies() followed by cy.clearAllCookies(); remove the
unnecessary cy.clearCookies() call and keep only cy.clearAllCookies() in the
beforeEach block so cookies are cleared across all domains without duplication
(locate the beforeEach in 065-sso-redirect-loop.spec.js and update the two calls
accordingly).
- Around line 167-171: The test currently inspects only inline HTML via
cy.document().then and misses external JS; update the test to locate any
<script> elements that reference the SSO script (e.g., find script elements
whose src contains "sso.js" or matches your SSO bundle name) and for each such
script use cy.request to fetch the external file and assert the response body
does not include "detectIncognito" or "is_incognito"; keep the existing inline
check (documentElement.outerHTML) but add these cy.get/cy.document queries and
cy.request assertions around the script src values so external script contents
are validated as well.
- Around line 199-203: The hardcoded cy.wait(2000) creates flakiness and
slowdown; remove the fixed wait and instead assert against the aliased request
with an explicit timeout by replacing the wait +
cy.get("@ssoRequest.all").should("have.length", 0) pattern with a single timed
assertion like cy.get("@ssoRequest.all", { timeout: 2000
}).should("have.length", 0) (or reduce the wait to a much smaller value if
removal isn't possible) and add a brief comment referencing the reason; locate
this change around the assertion that checks the "@ssoRequest.all" alias in the
065-sso-redirect-loop.spec.js test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 14f9b1e5-139a-4b88-8eb6-b6672461faea

📥 Commits

Reviewing files that changed from the base of the PR and between 4eb0a50 and dc45e8b.

📒 Files selected for processing (2)
  • .github/workflows/e2e.yml
  • tests/e2e/cypress/integration/065-sso-redirect-loop.spec.js

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant